package com.haoqizhe.kernel.elasticsearch.service.impl;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.haoqizhe.kernel.elasticsearch.common.utils.PageUtil;
import com.haoqizhe.kernel.elasticsearch.dao.ICrudEsDAO;
import com.haoqizhe.kernel.elasticsearch.service.ICrudEsService;
import com.haoqizhe.kernel.commons.enums.ConditionEnum;
import com.haoqizhe.kernel.commons.util.DateUtils;
import com.haoqizhe.kernel.core.entity.PageQuery;
import com.haoqizhe.kernel.core.entity.PageView;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;

/**
 * @author haoqizhe.li
 * @date 2018/9/5
 **/
@Slf4j
public abstract class AbstractCrudEsService<T,PK extends Serializable> implements ICrudEsService<T,PK> {

    /**
     * <p>获取repository</p>
     * @return
     * @author haoqizhe.li
     * @date 2018/11/19
     **/
    protected abstract ICrudEsDAO<T,PK> getRepository();
    private final Class<T> entity;

    protected AbstractCrudEsService(){
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        entity = (Class<T>) pt.getActualTypeArguments()[0];
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insert(T t){
        getRepository().save(t);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertBatch(List<T> list){
        getRepository().saveAll(list);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(T t){
        getRepository().save(t);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(PK pk){
        getRepository().deleteById(pk);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(T t){
        getRepository().delete(t);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteAll(){
        getRepository().deleteAll();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteAll(List<T> list){
        getRepository().deleteAll(list);
    }
    @Override
    public T get(PK pk){
        return (T)getRepository().findById(pk).get();
    }
    @Override
    public List<T> findAll(){
        return (List<T>)getRepository().findAll();
    }
    @Override
    public List<T> findAllById(List<PK> list){
        return (List<T>)getRepository().findAllById(list);
    }
    @Override
    public List<T> findBy(String word){
        QueryStringQueryBuilder builder=new QueryStringQueryBuilder(word);
        Iterable<T> feedIterable = getRepository().search(builder);

        List<T> list = new ArrayList<>();
        feedIterable.forEach(list::add);

        return list;
    }
    @Override
    public PageView<T> findBy(PageQuery<Map<String, Object>> pageQuery){
        //查询条件构造类
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        //过滤条件
        Map<String, Object> condition =pageQuery.getCondition();
        BoolQueryBuilder allQueryBuilder = getBoolQueryBuilder(condition);
        //分页
        PageRequest pageRequest = PageRequest.of(pageQuery.getPageNum()-1,pageQuery.getPageSize());
        searchQueryBuilder =setSortBuilder(searchQueryBuilder, pageQuery);
        //构造最终的查询条件
        NativeSearchQuery searchQuery = searchQueryBuilder.withQuery(allQueryBuilder).withPageable(pageRequest).build();
        Page<T> operateLogPage = getRepository().search(searchQuery);
        //构造结果
        PageView<T> page = PageUtil.getPageView(operateLogPage,pageQuery);

        return page;
    }
    @Override
    public PageView<T> findBy(String word, Integer pageNum,Integer pageSize){

        pageNum = pageNum == null ? 1 : pageNum;
        pageSize = pageSize == null ? 10 : pageSize;

        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        QueryStringQueryBuilder builder= new QueryStringQueryBuilder(word);
        //分页
        PageRequest pageRequest = PageRequest.of(pageSize-1,pageSize);

        NativeSearchQuery searchQuery = searchQueryBuilder.withQuery(builder).withPageable(pageRequest).build();

        Page<T> page = getRepository().search(searchQuery);
        //构造结果
        PageQuery pageQuery = new PageQuery(pageNum,pageSize);
        PageView<T> pageView = PageUtil.getPageView(page,pageQuery);

        return pageView;
    }
    /**
     * <p>处理搜索条件</p>
     * @param condition 前端处理的搜索条件
     * @return  
     * @author haoqizhe.li
     * @date 2018/9/8
     **/
    private BoolQueryBuilder getBoolQueryBuilder(Map<String, Object> condition){
        BoolQueryBuilder allQueryBuilder = QueryBuilders.boolQuery();

        if(condition != null){
            String prop = "",op = "";
            Object value = null;
            QueryBuilder queryBuilder = null;
            for(String key : condition.keySet()){
                prop = StrUtil.subBefore(key, "#",false);
                op = StrUtil.subAfter(key, "#",false);
                value = condition.get(key);

                boolean isNotEmpty = value instanceof  String ? StrUtil.isNotBlank(String.valueOf(value)):true;
                boolean isAnd = true;

                if(ObjectUtil.isNotNull(value) && isNotEmpty){
                    if(ConditionEnum.LIKE.code.equals(op)){
                        queryBuilder = QueryBuilders.wildcardQuery(prop,"*"+value.toString().trim()+"*");
                    }else if(ConditionEnum.GE.code.equals(op)){
                        queryBuilder = QueryBuilders.rangeQuery(prop).gte(getValue(prop,value));
                    }else if(ConditionEnum.GT.code.equals(op)){
                        queryBuilder = QueryBuilders.rangeQuery(prop).gt(getValue(prop,value));
                    }else if(ConditionEnum.IN.code.equals(op)){
                        queryBuilder = QueryBuilders.termsQuery(prop,Arrays.asList(String.valueOf(value).split(",")));
                    }else if(ConditionEnum.LE.code.equals(op)){
                        queryBuilder = QueryBuilders.rangeQuery(prop).lte(getValue(prop,value));
                    }else if(ConditionEnum.LT.code.equals(op)){
                        queryBuilder = QueryBuilders.rangeQuery(prop).lt(getValue(prop,value));
                    }else if(ConditionEnum.NULL.code.equals(op)){

                    }else if(ConditionEnum.NOT_NULL.code.equals(op)){

                    }else if(ConditionEnum.NOT_EQ.code.equals(op)){
                        queryBuilder = new QueryStringQueryBuilder(value.toString().trim()).field(prop);
                        isAnd = false;

                    }else if(ConditionEnum.NOT_LIKE.code.equals(op)){
                        queryBuilder = QueryBuilders.wildcardQuery(prop,"*"+value.toString().trim()+"*");
                        isAnd = false;

                    }else if(ConditionEnum.NOT_IN.code.equals(op)){
                        queryBuilder = QueryBuilders.termsQuery(prop,Arrays.asList(String.valueOf(value).split(",")));
                        isAnd = false;
                    }else{
                        queryBuilder = new QueryStringQueryBuilder(value.toString().trim()).field(prop);
                    }
                    if(isAnd){
                        allQueryBuilder.must(queryBuilder);
                    }else {
                        allQueryBuilder.mustNot(queryBuilder);
                    }
                }
            }
        }
        return allQueryBuilder;
    }
    private boolean isDate(String prop){
        Field field = ReflectUtil.getField(entity,prop);
        return field.getType().isAssignableFrom(Date.class);
    }

    private Object getValue(String prop,Object value){

        if(isDate(prop)){
            String str1 = "-";
            String str2 = "/";
            int dateLen = 10;
            String dateStr = value.toString();
            Date date = null;
            if(dateStr.length() == dateLen){
                if(dateStr.indexOf(str1) > -1){
                    date =  DateUtils.parse(dateStr,DateUtils.YYYY_MM_DD);
                }else if(dateStr.indexOf(str2) > -1){
                    date =  DateUtils.parse(dateStr,DateUtils.YYYYMMDD);
                }
            }else if(dateStr.length() > dateLen){
                if(dateStr.indexOf(str1) > -1){
                    date =  DateUtils.parse(dateStr,DateUtils.SDF_YMDHMS);
                }else if(dateStr.indexOf(str2) > -1){
                    date =  DateUtils.parse(dateStr,DateUtils.YYYYMMDDHHMMSS);
                }
            }
            return date.getTime();
        }else {
            return value;
        }
    }
    /**
     * <p>处理搜索排序条件</p>
     * @param searchQueryBuilder es搜索模板
     * @param pageQuery 排序条件Map
     * @return  
     * @author haoqizhe.li
     * @date 2018/9/8
     **/
    private NativeSearchQueryBuilder setSortBuilder(NativeSearchQueryBuilder searchQueryBuilder,PageQuery<Map<String, Object>> pageQuery){

        String orderBy = pageQuery.getOrderBy();
        if(StrUtil.isNotBlank(orderBy)){
            String[] orderBys = orderBy.split(",");
            SortOrder sortOrder;
            for(String order : orderBys){
                String[] ops = ArrayUtil.removeBlank(order.split(" "));
                sortOrder = ops.length == 2 ? "ASC".equals(ops[1].toUpperCase())? SortOrder.ASC :SortOrder.DESC: SortOrder.ASC;
                SortBuilder sortBuilder = SortBuilders.fieldSort(ops[0]).order(sortOrder);
                searchQueryBuilder.withSort(sortBuilder);
            }
        }
        return searchQueryBuilder;
    }

}
